package org.leviatan.definator.core.registry;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.leviatan.common.xml.XMLIO;
import org.leviatan.definator.core.admin.DefinatorConfigurationHandler;
import org.leviatan.definator.core.model.Field;
import org.leviatan.definator.core.model.Record;
import org.leviatan.definator.core.model.Table;
import org.leviatan.definator.core.model.impl.FieldImpl;
import org.leviatan.definator.core.model.impl.RecordImpl;
import org.w3c.dom.Document;


/* Singleton */
public class Registry {
	
	static Logger logger = Logger.getLogger(Registry.class);
	
	/* These maps contains <key, model object> */
	private static HashMap<String, Table> tableModels = null;
	private static HashMap<String, Record> recordModels = null;
	private static HashMap<String, Field> fieldModels = null;
	
	
	/* These maps contains only keys <tbl-key, Collection{rec-key1, rec-key2,...}> */
	/* Top-down mappings */
	private static HashMap<String, Vector<String>> tbl2recsMap = null;
	private static HashMap<String, Vector<String>> rec2fldsMap = null;
	
	/* These maps contains only keys <fld-key, rec-key> */
	/* Bottom-up mappings */
	private static HashMap<String, String> fld2recMap = null;
	private static HashMap<String, String> rec2tblMap = null;
	
	/* conatins only keys (long) */
	private static Vector unboundModels = null;
	
	private static Registry instance = null;
	
	
	/*
	static {
		DOMConfigurator.configure(DefinatorConfigurator.logConfFullPathName);
	}
	*/
	
	private Registry() {
		/* object registry */
		Registry.tableModels 	= new HashMap<String, Table>();
		Registry.recordModels 	= new HashMap<String, Record>();
		Registry.fieldModels 	= new HashMap<String, Field>();
		
		/* key registry */
		Registry.tbl2recsMap 	= new HashMap<String, Vector<String>>();
		Registry.rec2fldsMap 	= new HashMap<String, Vector<String>>();
		Registry.fld2recMap 	= new HashMap<String, String>();
		Registry.rec2tblMap 	= new HashMap<String, String>();
	}
	
	/* These build methods resides on the model objects and are only used when Registry's add
	 * methods for new modeles were not used. */
	public void fillTbl2RecsMap() {
		if (!Registry.tableModels.isEmpty()) {
			Record rec = new RecordImpl();
			String dsRef;
			for (Iterator<Record> i = Registry.recordModels.values().iterator(); i.hasNext() ;) {
				rec = i.next();
				dsRef = rec.getTableRef();
				
				//if ds doesnt exist yet, insert it and create empty vector for rec ids
				if (!Registry.tbl2recsMap.containsKey(dsRef)) {
					Registry.tbl2recsMap.put(dsRef, new Vector<String>());
				}
				//insert rec id
				Registry.tbl2recsMap.get(dsRef).add(rec.getId());
			}
		}
	}
	
	public void fillRec2FldsMap() {
		if (!Registry.recordModels.isEmpty()) {
			Field fld = new FieldImpl();
			String recRef;
			for (Iterator<Field> i = Registry.fieldModels.values().iterator(); i.hasNext() ;) {
				fld = i.next();
				recRef = fld.getRecordRef();
				
				//if ds doesnt exist yet, insert it and create empty vector for rec ids
				if (!Registry.rec2fldsMap.containsKey(recRef)) {
					Registry.rec2fldsMap.put(recRef, new Vector<String>());
				}
				//insert rec id
				Registry.rec2fldsMap.get(recRef).add(fld.getId());
			}
		}	
	}
	
	public void fillFld2RecMap() {
		if (!Registry.fieldModels.isEmpty()) {
			Field fld = new FieldImpl();
			String recRef;
			for (Iterator<Field> i = Registry.fieldModels.values().iterator(); i.hasNext() ;) {
				fld = i.next();
				recRef = fld.getRecordRef();
				
				// insert rec id
				Registry.fld2recMap.put(fld.getId(), recRef);
			}
		}
	}
	
	public void fillRec2TblMap() {
		if (!Registry.recordModels.isEmpty()) {
			Record rec = new RecordImpl();
			String dsRef;
			for (Iterator<Record> i = Registry.recordModels.values().iterator(); i.hasNext() ;) {
				rec = i.next();
				dsRef = rec.getTableRef();
				
				// insert rec id
				Registry.rec2tblMap.put(rec.getId(), dsRef);
			}
		}	
	}

	/* tested */
	public void loadTableModels() {
		Document doc = RegistryFileParser.loadFile(DefinatorConfigurationHandler.getActiveConfiguration().regPath + 
				DefinatorConfigurationHandler.getActiveConfiguration().tblRegFileName);
		Registry.tableModels = (HashMap<String, Table>) RegistryFileParser.parseTableDOM(doc);
		
		logger.info("Loaded table defs from file.");
	}
	
	/* tested */
	public void loadRecordModels() {
		Document doc = RegistryFileParser.loadFile(DefinatorConfigurationHandler.getActiveConfiguration().regPath +
				DefinatorConfigurationHandler.getActiveConfiguration().recRegFileName);
		Registry.recordModels = (HashMap<String, Record>) RegistryFileParser.parseRecDOM(doc);
		
		logger.info("Loaded record defs from file.");
	}
	
	/* tested */
	public void loadFieldModels() {
		Document doc = RegistryFileParser.loadFile(DefinatorConfigurationHandler.getActiveConfiguration().regPath +
				DefinatorConfigurationHandler.getActiveConfiguration().fldRegFileName);
		Registry.fieldModels = (HashMap<String, Field>) RegistryFileParser.parseFieldDOM(doc);
		
		logger.info("Loaded field defs from file.");
	}
	
	/* tested */
	public static Registry getInstance() {
		if (Registry.instance == null) {
			Registry.instance = new Registry();
		}
		return Registry.instance;
	}

	public HashMap<String, Table> getTableModels() {
		return Registry.tableModels;
	}

	public void setTableModels(HashMap<String, Table> datasources) {
		Registry.tableModels = datasources;
	}

	public HashMap<String, Field> getFieldModels() {
		return Registry.fieldModels;
	}

	public void setFieldModels(HashMap<String, Field> fields) {
		Registry.fieldModels = fields;
	}

	public HashMap<String, Record> getRecordModels() {
		return Registry.recordModels;
	}

	public void setRecordModels(HashMap<String, Record> records) {
		Registry.recordModels = records;
	}
	
	public void printTables() {
		for (Iterator<Table> i = tableModels.values().iterator(); i.hasNext(); ) {
			System.out.println( i.next() );
		}
	}
	
	public void printRecords() {
		for (Iterator<Record> i = recordModels.values().iterator(); i.hasNext(); ) {
			System.out.println( i.next() );
		}
	}
	
	public void printFields() {
		for (Iterator<Field> i = fieldModels.values().iterator(); i.hasNext(); ) {
			System.out.println( i.next() );
		}
	}


	public HashMap<String, Vector<String>> getTbl2RecsMap() {
		return Registry.tbl2recsMap;
	}

	public void setTbl2recsMap(HashMap<String, Vector<String>> ds2recsMap) {
		Registry.tbl2recsMap = ds2recsMap;
	}

	public HashMap<String, String> getFld2RecsMap() {
		return Registry.fld2recMap;
	}

	public void setFld2RecMap(HashMap<String, String> fld2recsMap) {
		Registry.fld2recMap = fld2recsMap;
	}

	public HashMap<String, String> getRec2TblMap() {
		return Registry.rec2tblMap;
	}

	public void setRec2TblMap(HashMap<String, String> rec2dsMap) {
		Registry.rec2tblMap = rec2dsMap;
	}

	public HashMap<String, Vector<String>> getRec2FldsMap() {
		return Registry.rec2fldsMap;
	}

	public void setRec2FldsMap(HashMap<String, Vector<String>> rec2fldsMap) {
		Registry.rec2fldsMap = rec2fldsMap;
	}
	
	
	
	///////////////////////////////////////////////////////////////////////////
	
	public void addTableModel(Table tbl) {
		Registry.tableModels.put(tbl.getId(), tbl);
		// create empty vector with rec refs:
		Registry.tbl2recsMap.put(tbl.getId(), new Vector<String>());
	}
	
	public void addRecordModel(Record rec) {
		Registry.recordModels.put(rec.getId(), rec);
		Registry.rec2fldsMap.put(rec.getId(), new Vector<String>());
	}
	
	public void addFieldModel(Field fld) {
		Registry.fieldModels.put(fld.getId(), fld);
	}
	
	public void addField2RecordMapping(String fldId, String recId) {
		Registry.fld2recMap.put(fldId, recId);
	}
	
	public void addRecord2TableMapping(String recId, String tblId) {
		Registry.rec2tblMap.put(recId, tblId);
	}
	
	
	public void addTable2RecordMapping(String tblId, String recId) {
		if (!Registry.tbl2recsMap.containsKey(tblId)) {
			Vector<String> newRecIdVector = new Vector<String>();
			newRecIdVector.add(recId);
			Registry.tbl2recsMap.put(tblId, newRecIdVector);		
		} else {
			Registry.tbl2recsMap.get(tblId).add(recId);
		}
	}
	
	public void addRecord2FieldMapping(String recId, String fldId) {
		if (!Registry.rec2fldsMap.containsKey(recId)) {
			Vector<String> newFldIdVector = new Vector<String>();
			newFldIdVector.add(fldId);
			Registry.rec2fldsMap.put(recId, newFldIdVector);		
		} else {
			Registry.rec2fldsMap.get(recId).add(fldId);
		}	
	}
	
	public void removeTableMappingsCascading(String tblId) {
		// TODO
	}
	
	public void removeRecordMappingsCascading(String recId) {
		// TODO
	}
	
	public boolean removeField2RecordMapping(String fldId) {
		return (Registry.fld2recMap.remove(fldId) == null ? false : true); 
	}
	
	public boolean removeRecord2TableMapping(String recId) {
		return (Registry.rec2tblMap.remove(recId) == null ? false : true);
	}
	
	public boolean removeTable2RecordMapping(String tblId) {
		Vector v = (Vector) Registry.tbl2recsMap.get(tblId);
		if (v == null) return false;
		v.removeAllElements();
		return true;
	}
	
	public boolean removeRecord2FieldMapping(String recId) {
		Vector v = (Vector) Registry.rec2fldsMap.get(recId);
		if (v == null) return false;
		v.removeAllElements();
		return true;
	}
	
	
	
	// TODO TEST
	public void saveTableModelsToFile() {
		Document tableXmlDoc = 
			XMLIO.getInstance().readXMLString(this.createTableXmlString());
		String filePath = DefinatorConfigurationHandler.getActiveConfiguration().regPath + 
			DefinatorConfigurationHandler.getActiveConfiguration().tblRegFileName;
		XMLIO.getInstance().writeDocumentToXMLFile(tableXmlDoc, filePath);
	}
	
	// TODO TEST
	public void saveRecordModelsToFile() {
		Document recordXmlDoc =
			XMLIO.getInstance().readXMLString(this.createRecordXmlString());
		String filePath = DefinatorConfigurationHandler.getActiveConfiguration().regPath + 
			DefinatorConfigurationHandler.getActiveConfiguration().recRegFileName;
		XMLIO.getInstance().writeDocumentToXMLFile(recordXmlDoc, filePath);
	}
	
	// TODO TEST
	public void saveFieldModelsToFile() {
		Document fieldXmlDoc =
			XMLIO.getInstance().readXMLString(this.createFieldXmlString());
		String filePath = DefinatorConfigurationHandler.getActiveConfiguration().regPath + 
			DefinatorConfigurationHandler.getActiveConfiguration().fldRegFileName;
		XMLIO.getInstance().writeDocumentToXMLFile(fieldXmlDoc, filePath);
	}
	
	
	
	
	public String createTableXmlString() {
		String xmlStart = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n";
		String xmlAcc = "<tables>\n";
		Table tbl;
		for (Iterator i = Registry.tableModels.values().iterator(); i.hasNext(); ) {
			xmlAcc += "\t<table>\n";
			
			tbl = (Table) i.next();
			
			xmlAcc += "\t\t<id>" + tbl.getId() + "</id>\n";
			xmlAcc += "\t\t<name>" + tbl.getName() + "</name>\n";
			xmlAcc += "\t</table>\n";
		}
		
		xmlAcc += "</tables>\n";
		return xmlStart + xmlAcc;
	}
	
	public String createRecordXmlString() {
		String xmlStart = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n";
		String xmlAcc = "<records>\n";
		Record rec;
		for (Iterator i = Registry.recordModels.values().iterator(); i.hasNext(); ) {
			xmlAcc += "\t<record>\n";
			
			rec = (Record) i.next();
			
			xmlAcc += "\t\t<id>" + rec.getId() + "</id>\n";
			xmlAcc += "\t\t<name>" + rec.getName() + "</name>\n";
			xmlAcc += "\t\t<table-ref-id>" + rec.getTableRef() + "</table-ref-id>\n";
			
			xmlAcc += "\t</record>\n";
		}
		
		xmlAcc += "</records>\n";
		return xmlStart + xmlAcc;
	}
	
	public String createFieldXmlString() {
		String xmlStart = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
		String xmlAcc = "<fields>\n";
		Field field;
		for (Iterator i = Registry.fieldModels.values().iterator(); i.hasNext(); ) {
			xmlAcc += "\t<field>\n";
			
			field = (Field) i.next();
			
			xmlAcc += "\t\t<id>" + field.getId() + "</id>\n";
			xmlAcc += "\t\t<name>" + field.getName() + "</name>\n";
			xmlAcc += "\t\t<record-ref-id>" + field.getRecordRef() + "</record-ref-id>\n";
			
			xmlAcc += "\t\t<data-type>" + field.getType() + "</data-type>\n";
			xmlAcc += "\t\t<data-length>" + field.getLength() + "</data-length>\n";
			
			xmlAcc += "\t</field>\n";
		}
		
		xmlAcc += "</fields>\n";
		return xmlStart + xmlAcc;
	}
	
	
	
}
